<!-- 图片上传组件 -->
<template>
  <!-- 实际的上传组件（隐藏） -->
  <div style="display: none">
    <el-upload ref="uploadRef" list-type="text" v-model:file-list="fileList" :http-request="customRequest"
      :before-upload="handleBeforeUpload" :action="props.action" :headers="props.headers" :data="props.data"
      :name="props.name" :on-success="handleSuccessFile" :on-error="handleError" :accept="props.accept"
      :limit="props.limit">
    </el-upload>
  </div>

  <!-- 自定义的显示区域 -->
  <div class="custom-upload-list">
    <!-- 已上传的图片列表 -->
    <div v-for="(path, index) in valFileList" :key="index" class="custom-upload-item">
      <img class="upload-thumbnail" :src="path" alt="" />
      <div class="upload-actions">
        <span class="action-item preview" @click="previewImg(path)">
          <el-icon><zoom-in /></el-icon>
        </span>
        <span v-if="props.showDelBtn" class="action-item delete" @click="handleRemove(path)">
          <el-icon>
            <Delete />
          </el-icon>
        </span>
      </div>
    </div>

    <!-- 上传按钮 -->
    <div v-if="valFileList.length < props.limit && props.showUploadBtn" class="custom-upload-trigger"
      @click="triggerUpload">
      <el-icon>
        <Plus />
      </el-icon>
    </div>
  </div>

  <!-- 图片预览组件 -->
  <el-image-viewer v-if="viewVisible" :zoom-rate="1.2" :initialIndex="initialIndex" :url-list="viewFileList"
    @close="closePreview" />
</template>
<script setup lang="ts">
import FileAPI from "@/api/file";
import { getToken } from "@/utils/auth";
import { MD5, lib } from "crypto-js";
import { UploadFile, UploadRawFile, UploadUserFile } from "element-plus";

const emit = defineEmits(["update:modelValue", "change"]);

const props = defineProps({
  /**
   * 文件路径集合
   */
  modelValue: {
    type: [Array, String],
    default: () => [],
  },
  /**
   * 上传地址
   */
  action: {
    type: String,
    default: FileAPI.uploadUrl,
  },
  viewShow: {
    type: Boolean,
    default: true,
  },
  /**
   * 请求头
   */
  headers: {
    type: Object,
    default: () => {
      return {
        Authorization: getToken(),
      };
    },
  },
  /**
   * 请求携带的额外参数
   */
  data: {
    type: Object,
    default: () => {
      return {};
    },
  },
  /**
   * 上传文件的参数名
   */
  name: {
    type: String,
    default: "file",
  },
  /**
   * 文件上传数量限制
   */
  limit: {
    type: Number,
    default: 10,
  },
  /**
   * 是否显示删除按钮
   */
  showDelBtn: {
    type: Boolean,
    default: true,
  },
  /**
   * 是否显示上传按钮
   */
  showUploadBtn: {
    type: Boolean,
    default: true,
  },
  /**
   * 单张图片最大大小,单位MB
   */
  maxSize: {
    type: Number,
    default: 10,
  },
  /**
   * 上传文件类型
   */
  accept: {
    type: String,
    default: "image/*",
  },
  /**
   * 支持的文件类型,默认支持所有图片格式
   * eg:['png','jpg','jpeg','gif']
   */
  supportFileType: {
    type: Array<string>,
    default: () => [],
  },
  /**
   * 自定义样式
   */
  style: {
    type: Object,
    default: () => {
      return {
        width: "130px",
        height: "130px",
      };
    },
  },
});

const viewVisible = ref(false);
const initialIndex = ref(0);

const fileList = ref([] as UploadUserFile[]);
const valFileList = ref([] as string[]);
const viewFileList = ref([] as string[]);

// 添加一个ref来引用el-upload组件
const uploadRef = ref();

const customRequest = (file: any) => {
  return new Promise((resolve, reject) => {
    FileAPI.chunkReady({ filename: file.file.name }).then((res: any) => {
      console.log(res);
      let uk = res.uk;
      chunkFile(file).then((chunkList: any) => {
        let promiseList = [];
        for (let i: any = 0; i < chunkList.length; i++) {
          promiseList.push(
            new Promise((resolve2, reject2) => {
              const formData = new FormData();
              formData.append("file", chunkList[i].content);
              formData.append("uk", uk);
              formData.append("total", chunkList.length);
              formData.append("idx", i + 1);
              formData.append("md5", chunkList[i].md5);

              FileAPI.chunk(formData).then((step2Res: any) => {
                resolve2(step2Res);
              });
            })
          );
        }

        // 上传所有分片
        Promise.all(promiseList)
          .then((resList) => {
            // 上传的结果里面只会有一个请求返回成功

            let resData = null;
            resList.forEach((item: any) => {
              if (item.url !== undefined) {
                resData = item;
              }
            });

            if (resData === null) {
              reject(new Error("返回数据有误"));
            } else {
              resolve(resData);
            }
          })
          .catch((err) => {
            reject(err);
          });
      })
    })
  })
}

/**
     * 对文件进行分片
     * @param {File} file
     */
const chunkFile = ({ file }: any) => {
  // 每片的大小
  let chunkSize = 1 * 1024 * 1024;
  let cnt = Math.ceil(file.size / chunkSize)
  let list: any = {
    length: cnt,
  };
  let pList = [];

  for (let i = 0; i < cnt; i++) {

    pList.push(new Promise<void>((resolve, reject) => {
      let tmp: any = '';
      let start = i * chunkSize;
      let end = Math.min(file.size, start + chunkSize);

      // 切割文件
      tmp = file.slice(start, end);
      let reader = new FileReader();
      reader.onload = function (ev: any) {

        // 每个片里面装的是 base64编码后的内容
        // 去掉 base64,前面的的内容
        let p: any = ev.target.result;
        // let p = '';
        // if (base64Str.indexOf('base64,') > -1) {
        //     p = base64Str.split('base64,')[1];
        // } else {
        //     p = base64Str;
        // }
        // p = str.safeEncodeURL(p);
        // 这里因为 onload是异步执行，所以用push()会导致乱序
        list[i] = {
          content: tmp,
          md5: MD5(lib.WordArray.create(p)).toString()
        };
        resolve()
      }
      reader.onerror = function (er) {
        reject(er)
      }
      // reader.readAsDataURL(tmp)
      reader.readAsArrayBuffer(tmp)

    }))

  }

  return new Promise((res, rej) => {
    Promise.all(pList).then(() => {
      res(list)
    })
      .catch(err => {
        rej(err);
      })
  })
}

// const start = currentChunk.value * chunkSize;
//   const end = Math.min(start + chunkSize, file.size);
//   const chunk = file.slice(start, end); // 获取当前分片
//   console.log(chunk)

watch(
  () => props.modelValue,
  (newVal) => {
    if (typeof newVal === "string" && !newVal) {
      fileList.value = [];
      viewFileList.value = [];
      valFileList.value = [];
      return;
    }
    const modelValue = typeof newVal === "string" ? [newVal] : (newVal as string[]);
    const filePaths = fileList.value.map((file) => file.url);
    // 监听modelValue文件集合值未变化时，跳过赋值
    if (
      filePaths.length > 0 &&
      filePaths.length === modelValue.length &&
      filePaths.every((x) => modelValue.some((y) => y === x)) &&
      modelValue.every((y) => filePaths.some((x) => x === y))
    ) {
      return;
    }

    if (modelValue.length <= 0) {
      fileList.value = [];
      viewFileList.value = [];
      valFileList.value = [];
      return;
    }

    fileList.value = modelValue.map((filePath) => {
      return { url: filePath } as UploadUserFile;
    });
    valFileList.value = modelValue;
  },
  { immediate: true }
);

/**
 * 上传成功回调
 *
 * @param options
 */
const handleSuccessFile = (response: any, file: UploadFile) => {
  ElMessage.success("上传成功");
  console.log(response)
  valFileList.value.push(response.url);
  if (props.limit == 1) {
    emit("update:modelValue", response.url);
    emit("change", response.url);
  } else {
    emit("update:modelValue", valFileList.value);
    emit("change", valFileList.value);
  }
  return;
};

const handleError = (error: any) => {
  ElMessage.error("上传失败");
};

/**
 * 删除图片
 */
function handleRemove(path: string) {
  if (path) {
    valFileList.value = valFileList.value.filter((x) => x !== path);
    // 删除成功回调
    if (props.limit === 1) {
      emit("update:modelValue", "");
      emit("change", "");
    } else {
      emit("update:modelValue", valFileList.value);
      emit("change", valFileList.value);
    }
  }
}

/**
 * 限制用户上传文件的格式和大小
 */
const chunkSize = 1024 * 1024; // 设置每个分片大小为 1MB
const currentChunk = ref(0); // 当前分片
const totalChunks = ref(0); // 总分片数
function handleBeforeUpload(file: UploadRawFile) {

  const total = Math.ceil(file.size / chunkSize); // 计算总分片数
  currentChunk.value = 0; // 重置当前分片
  totalChunks.value = total; // 更新总分片数

  // 限制文件大小
  if (file.size > props.maxSize * 1024 * 1024) {
    ElMessage.warning("上传图片不能大于" + props.maxSize + "M");
    return false;
  }
  // 判断文件类型
  // 获取文件后缀名
  const fileExt = file.name.split(".").pop();
  if (!fileExt) {
    ElMessage.warning("上传图片格式错误,支持的文件类型:" + props.supportFileType.join(","));
    return false;
  }
  // 给文件后缀名转换为小写
  const lowerCaseFileExt = fileExt.toLowerCase();
  // 判断文件后缀名是否在支持的文件类型中
  if (props.supportFileType.length > 0) {
    let isSupport = false;
    props.supportFileType.forEach((type) => {
      if (type.toLowerCase() === lowerCaseFileExt) {
        isSupport = true;
      }
    });
    if (!isSupport) {
      ElMessage.warning("上传图片格式错误,支持的文件类型:" + props.supportFileType.join(","));
      return false;
    }
  }
  return true;
}

/**
 * 预览图片
 */
const previewImg = (path: string) => {
  if (props.viewShow) {
    viewFileList.value = fileList.value.map((file) => file.url!);
    initialIndex.value = fileList.value.findIndex((file) => file.url === path);
    viewVisible.value = true;
  }
};

/**
 * 关闭预览
 */
const closePreview = () => {
  viewVisible.value = false;
};

// 修改triggerUpload方法
const triggerUpload = () => {
  // 通过ref直接访问el-upload组件内的input元素
  const uploadEl = uploadRef.value.$el.querySelector(".el-upload__input");
  if (uploadEl) {
    uploadEl.click();
  }
};
</script>
<style lang="scss" scoped>
.custom-upload-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.custom-upload-item {
  position: relative;
  width: v-bind("props.style.width");
  height: v-bind("props.style.height");
  overflow: hidden;
  border-radius: 6px;

  .upload-thumbnail {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .upload-actions {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background-color: rgb(0 0 0 / 50%);
    opacity: 0;
    transition: opacity 0.3s;

    &:hover {
      opacity: 1;
    }

    .action-item {
      padding: 8px;
      color: #fff;
      cursor: pointer;

      &:hover {
        color: var(--el-color-primary);
      }
    }
  }
}

.custom-upload-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: v-bind("props.style.width");
  height: v-bind("props.style.height");
  cursor: pointer;
  background-color: rgb(255 254 254 / 50%);
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  transition: var(--el-transition-duration);

  &:hover {
    color: var(--el-color-primary);
    border-color: var(--el-color-primary);
  }

  .el-icon {
    font-size: 20px;
    color: #999;
  }

  &:hover .el-icon {
    color: var(--el-color-primary);
  }
}
</style>
